#include <string.h>
#include <stdint.h>
#include "at_common.h"
#include "at_parser.h"
#include "at_uart_hal.h"


#define OOB_MAX 5
#define AT_WORKER_STACK_SIZE 256
#define AT_UART_TIMEOUT_MS 1000
#define RECV_BUFFER_SIZE 512

typedef struct oob_s
{
    char *prefix;
    uint32_t reallen;
    at_recv_cb cb;
    void *arg;
} oob_t;

typedef struct at_task_s
{
    void *smpr;
    char *command;
    char *rsp;
    char *rsp_prefix;
    char *rsp_fail_postfix;
    uint32_t rsp_offset;
    uint32_t rsp_len;
    bool result;
} at_task_t;

/**
 * Parser structure for parsing AT commands
 */
typedef struct
{
    at_task_t task;
    oob_t _oobs[OOB_MAX];
    int _oobs_num;

    void *at_uart_recv_mutex;
    void *at_uart_send_mutex;
    void *task_mutex;
    void *worker_task;

    char recv_buf[RECV_BUFFER_SIZE];
    uint16_t recv_counter;
} at_parser_t;

static uint8_t inited = 0;
static at_parser_t at;

static void at_worker(void *arg);

bool at_parser_deinit()
{
    AT_INFO("at parser deinit");
    inited=0;
    at_uart_deinit();
    if (at.worker_task != NULL)
        vTaskDelete(at.worker_task);

    if (at.at_uart_recv_mutex != NULL)
        vSemaphoreDelete(at.at_uart_recv_mutex);
    if (at.at_uart_send_mutex != NULL)
        vSemaphoreDelete(at.at_uart_send_mutex);
    if (at.task_mutex != NULL)
        vSemaphoreDelete(at.task_mutex);
    
    memset(&at, 0, sizeof(at_parser_t));

    AT_INFO("at parser deinit ok");
    return false;
}

bool at_parser_init(void)
{

    if (inited == 1)
    {
        AT_INFO("at parser have already inited");
        return true;
    }

    memset(&at, 0, sizeof(at_parser_t));
    //memset(at._oobs, 0, sizeof(oob_t) * OOB_MAX);

    if (!at_uart_init())
    {
        AT_ERROR("at uart init fail");
        return false;
    }

    at.at_uart_recv_mutex = xSemaphoreCreateMutex();
    if (at.at_uart_recv_mutex == NULL)
    {
        AT_ERROR("at uart recvmutex init fail");
        goto AT_PARSER_INIT_ERR;
    }

    at.at_uart_send_mutex = xSemaphoreCreateMutex();
    if (at.at_uart_send_mutex == NULL)
    {
        AT_ERROR("at uart send mutex init fail");
        goto AT_PARSER_INIT_ERR;
    }

    at.task_mutex = xSemaphoreCreateMutex();
    if (at.task_mutex == NULL)
    {
        AT_ERROR("at task mutex init fail");
        goto AT_PARSER_INIT_ERR;
    }

    if (xTaskCreate(at_worker, "at_worker", AT_WORKER_STACK_SIZE, NULL, 3, &at.worker_task) != pdTRUE)
    {
        AT_ERROR("fail to creat at task");
        goto AT_PARSER_INIT_ERR;
    }
    return true;

AT_PARSER_INIT_ERR:
    at_parser_deinit();
    return false;
}

bool at_send_wait_reply(const char *cmd, uint16_t cmdlen, bool delimiter,
                        char *prefix, char *fail_prefix,
                        char *replybuf, int *replysize,
                        uint16_t timeout)
{
    bool ret = false;

    if (inited == 0)
    {
        AT_ERROR("at have not init yet\r\n");
        return false;
    }

    if (xSemaphoreTake(at.at_uart_send_mutex, timeout) == pdFALSE)
    {
        return false;
    }

    memset(&at.task,0,sizeof(at_task_t));
    at.task.command = (char *)cmd;
    at.task.rsp = replybuf;
    if(*replysize==NULL)at.task.rsp_len = 0;
    else at.task.rsp_len = *replysize;

    at.task.smpr = xSemaphoreCreateBinary();
    if (NULL == at.task.smpr)
    {
        AT_ERROR("failed to allocate semaphore");
        goto end;
    }

    if (NULL == prefix)
    {
        AT_ERROR("at send prefix is null");
    }
    at.task.rsp_prefix = prefix;

    if (NULL != fail_prefix)
    {
        at.task.rsp_fail_postfix = fail_prefix;
    }
    else
    {
        at.task.rsp_fail_postfix = AT_RECV_FAIL_POSTFIX;
    }
    

    AT_INFO("at send: %d,%s", cmdlen, cmd);
    if (at_uart_send(cmd, cmdlen, timeout) != cmdlen)
    {
        AT_ERROR("uart send command failed");
        goto end;
    }

    if (delimiter)
    {
        if (at_uart_send(AT_SEND_DELIMITER, strlen(AT_SEND_DELIMITER), timeout) != strlen(AT_SEND_DELIMITER))
        {
            AT_ERROR("uart send delimiter failed");
            goto end;
        }
    }

    if (xSemaphoreTake(at.task.smpr, timeout) != pdTRUE)
    {
        AT_ERROR("sem_wait failed");
        goto end;
    }
    if(at.task.result)
    {
        ret=true;
        if(replysize!=NULL)*replysize = at.task.rsp_len;
    }

end:
    if(at.task.smpr!=NULL)vSemaphoreDelete(at.task.smpr);
    memset(&at.task,0,sizeof(at_task_t));
    xSemaphoreGive(at.at_uart_send_mutex);
    return ret;
}

bool at_send_no_reply(const char *data, int datalen, bool delimiter, uint32_t timeout)
{
    bool ret = false;

    if (inited == 0)
    {
        AT_ERROR("at have not init yet");
        return false;
    }

    if (NULL == data || datalen <= 0)
    {
        AT_ERROR("invalid input");
        return false;
    }

    if (xSemaphoreTake(at.at_uart_send_mutex, timeout) == pdFALSE)
    {
        return false;
    }

    AT_INFO("at send: %d,%s", datalen, data);

    if (at_uart_send(data, datalen, timeout) != datalen)
    {
        AT_ERROR("uart send data failed", data);
        goto SEND_NO_REPLAY_END;
    }

    if (delimiter)
    {
        if (at_uart_send(AT_SEND_DELIMITER, strlen(AT_SEND_DELIMITER), timeout) != strlen(AT_SEND_DELIMITER))
        {
            AT_ERROR("uart send delimiter failed");
            goto SEND_NO_REPLAY_END;
        }
    }
    ret = true;

SEND_NO_REPLAY_END:
    xSemaphoreGive(at.at_uart_send_mutex);
    return ret;
}

static bool at_getc(char *c, int timeout_ms)
{
    char data;
    uint32_t recv_size = 0;

    if (NULL == c)
        return false;

    if (inited == 0)
    {
        AT_ERROR("at have not init yet\r\n");
        return false;
    }

    if (xSemaphoreTake(at.at_uart_recv_mutex, timeout_ms) == pdFALSE)
        return false;
    recv_size = at_uart_receive(&data, 1, timeout_ms);
    xSemaphoreGive(at.at_uart_recv_mutex);

    if (recv_size != 1)
        return false;
    *c=data;
    return true;
}

bool at_register_callback(const char *prefix, at_recv_cb cb, void *arg)
{
    oob_t *oob = NULL;
    int i = 0;

    if (NULL == prefix)
    {
        AT_ERROR("prefix  is null");
        return false;
    }

    if (at._oobs_num >= OOB_MAX)
    {
        AT_ERROR("No place left in OOB.");
        return false;
    }

    /*check oob exist*/
    for (i = 0; i < at._oobs_num; i++)
    {
        if (NULL != at._oobs[i].prefix &&
            strcmp(prefix, at._oobs[i].prefix) == 0)
        {
            AT_WARNING("oob prefix %s is already exist.", prefix);
            return false;
        }
    }

    oob = &(at._oobs[at._oobs_num++]);
    oob->prefix = (char *)prefix;
    oob->cb = cb;
    oob->arg = arg;

    AT_DEBUG("New oob registered (%s)", oob->prefix);

    return true;
}

static bool at_scan_for_callback(char *buf, int buflen)
{
    int k;
    oob_t *oob = NULL;

    for (k = 0; k < at._oobs_num; k++)
    {
        oob = &(at._oobs[k]);
        if (strstr(buf, oob->prefix) != NULL)
        {
            oob->cb(oob->arg, buf, buflen);
            return true;
        }
    }
    return false;
}

static void at_worker(void *arg)
{
    char c = 0;
    AT_DEBUG("at_work started.");

    memset(at.recv_buf, 0, RECV_BUFFER_SIZE);
    inited = 1;

    while (true)
    {
        if (at_getc(&c, 1000) != true)
        {
            continue;
        }

        at.recv_buf[at.recv_counter++] = c;
        if (c != AT_RECV_END_CHAR)
        {
            if (at.recv_counter >= (RECV_BUFFER_SIZE - 1))
            {
                at.recv_buf[at.recv_counter] = '\0';
                AT_ERROR("AT recv buffer full!");
                goto RECV_MATCH;
            }
            continue;
        }
        else if (at.recv_counter <= 2)
        {
            goto CLEAN_BUF;
        }
        else
        {
            at.recv_buf[at.recv_counter - 2] = '\0';
            at.recv_counter -= 1; //include '\0'
        }

    RECV_MATCH:
        AT_INFO("at recv :%d,%s", at.recv_counter, at.recv_buf);

        /*  match oob  */
        if (at_scan_for_callback(at.recv_buf, at.recv_counter))
            goto CLEAN_BUF;

        else if ((NULL != at.task.rsp_prefix) && (strstr(at.recv_buf, at.task.rsp_prefix) != NULL))
        {
            goto RECV_SUCCESS;
        }
        else if ((NULL != at.task.rsp_fail_postfix) && (strstr(at.recv_buf, at.task.rsp_fail_postfix) != NULL))
        {
            goto RECV_FAIL;
        }
        else
        {
            goto CLEAN_BUF;
        }

    RECV_FAIL:
        at.task.rsp_len = 0;
        if (at.task.smpr != NULL)
        {
            at.task.result=false;
            xSemaphoreGive(at.task.smpr);
        }
        memset(at.recv_buf, 0, RECV_BUFFER_SIZE);
        at.recv_counter = 0;
        continue;
    RECV_SUCCESS:
        if (at.task.rsp != NULL && at.task.rsp_len != 0) //need copy
        {
            if (at.task.rsp_len < at.recv_counter)
            {
                memcpy(at.task.rsp, at.recv_buf, at.task.rsp_len - 1);
                at.task.rsp[at.task.rsp_len - 1] = '\0';
            }
            else
            {
                memcpy(at.task.rsp, at.recv_buf, at.recv_counter);
                at.task.rsp_len = at.recv_counter;
            }
        }
        if (at.task.smpr != NULL)
        {
            at.task.result=true;
            xSemaphoreGive(at.task.smpr);
        }
        memset(at.recv_buf, 0, RECV_BUFFER_SIZE);
        at.recv_counter = 0;
        continue;
    CLEAN_BUF:
        memset(at.recv_buf, 0, RECV_BUFFER_SIZE);
        at.recv_counter = 0;
        continue;
    }
}
